﻿/*
 *  类库名称 ：editor
 *  类库描述 ：编辑器类库（使用本类，必须在本文件之前按顺序引用jQun.js、dropDownList.js、fileInput.js）。
 *  文档状态 ：1.0.1.3
 *  本次修改 ：将文件相关的代码提出，作为fileInput.js并扩充其内容。
 *  开发浏览器信息 ：firefox 20.0+、chrome 26.0+、IE 10+、基于webkit的手机浏览器
 */

new function(
	jQun,
	Class, StaticClass, Enum, Interface, HTMLElementList, HTML, Event,
	LINE_BREAK,
	document, selection,
	defineProperties
){

// 一些基本UI相关
(function(floor){

this.DraggableAreaList = (function(draggingEvent, windowList, getPoint){
	function DraggableAreaList(selector){
		///	<summary>
		///	可拖动的区域。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var draggableList = this, isPress = false;

		this.attach({
			mousedown : function(e){
				isPress = true;
				
				draggableList.dispatch(
					draggingEvent,
					{
						point : getPoint(
							e,
							draggableList.rect()
						)
					}
				);
			}
		});

		windowList.attach({
			mouseup : function(){
				isPress = false;
			},
			mousemove : function(e){
				if(!isPress)
					return;
				
				draggableList.dispatch(
					draggingEvent,
					{
						point : getPoint(
							e,
							draggableList.rect()
						)
					}
				);
			}
		});
	};
	DraggableAreaList = new Class(DraggableAreaList, "jQun.DraggableAreaList", HTMLElementList.prototype);

	return DraggableAreaList.constructor;
}(
	// draggingEvent
	new Event("dragging"),
	// windowList
	new HTMLElementList(window),
	// getPoint
	function(e, rect){
		var left = floor(e.clientX - rect.left), top = floor(e.clientY - rect.top);

		if(left >= rect.width){
			left = rect.width;
		}
		else if(left < 0){
			left = 0;
		}

		if(top >= rect.height){
			top = rect.height;
		}
		else if(top < 0){
			top = 0;
		}

		return [left, top];
	}
));

this.SimpleButton = (function(html, buttonclickedEvent){
	function SimpleButton(action, _title, _text){
		///	<summary>
		///	简单按钮控件。
		///	</summary>
		///	<param name="action" type="String">该类容器的data-action值。</param>
		///	<param name="_title" type="String">鼠标hover状态下的标题。</param>
		///	<param name="_text" type="String">需要显示的文本值。</param>
		this.combine(
			html.create(
				{ action : action, title : _title, text : _text }
			)
		);

		this.query(
				"button"
			)
			.attach({
				click : function(){
					buttonclickedEvent.trigger(
						this,
						{ action : action }
					);
				}
			});
	};
	SimpleButton = new Class(SimpleButton, "jQun.SimpleButton", HTMLElementList.prototype);

	return SimpleButton.constructor;
}(
	// html
	new HTML([
		'<div class="simpleButton" data-action="{action}">',
			'<button title="{?~title}">{?~text}</button>',
		'</div>'
	].join("")),
	// buttonclickedEvent
	new Event("buttonclicked")
));

this.ToolBarGroup = (function(){
	function ToolBarGroup(selector){
		///	<summary>
		///	工具栏分组类。
		///	</summary>
		///	<param name="selector" type="String">选择器。</param>
	};
	ToolBarGroup = new Class(ToolBarGroup, "jQun.ToolBarGroup", HTMLElementList.prototype);

	return ToolBarGroup.constructor;
}());

}.call(
	this,
	Math.floor
));


// 颜色相关
(function(Text){

this.ColorNames = (function(){
	return new Enum(
		["Red", "Green", "Blue"]
	);
}());

this.Color = (function(
	ColorNames,
	WIDTH, HEIHT, RGBA_REGX, ARGUMENTS_SPLIT_REGX,
	canvas, context, rgbText, rgbaText,
	every, toArray
){
	function Color(){
		///	<summary>
		///	颜色类，提供各种颜色的获取及转换方法。
		///	</summary>
		canvas.width = WIDTH;
		canvas.height = HEIHT;

		context = canvas.getContext("2d");
	};
	Color = new StaticClass(Color, "jQun.Color");

	Color.props({
		compare : function(first, second){
			///	<summary>
			///	比较2种颜色是否一样。
			///	</summary>
			///	<param name="first" type="String, ImageData">需要比较的第一种颜色。</param>
			///	<param name="second" type="String, ImageData">需要比较的第二种颜色。</param>
			return every(
				this.getData(first),
				function(val, name){
					return val === this[name];
				},
				this.getData(second)
			);
		},
		getData : function(color){
			///	<summary>
			///	将颜色字符串转化成颜色数据数组，[红, 绿, 蓝, 透明度]。
			///	</summary>
			///	<param name="color" type="String, ImageData">给定的颜色。</param>
			var data;

			context.clearRect(0, 0, WIDTH, HEIHT);

			if(typeof color === "string"){
				/*
					所有浏览器，对RGBA模式的填充颜色都不准确，情况如下：
						1： 所有透明度不为1的时候，用getImageData获取的颜色值有偏差
						2： 透明度为0的时候，用getImageData获取的数据是[0, 0, 0, 0]！！

					对于第一个的解释是：透明度乘以255后，得到alpha值，此值必须为整数，才能获取正确；
					对于第二个的解释是：canvas不会记录填充的颜色。
				*/
				var result = color.match(RGBA_REGX);

				if(result){
					return result[1].split(
						ARGUMENTS_SPLIT_REGX
					).map(function(val){
						return val - 0;
					});
				}

				context.fillStyle = color;
				context.fillRect(0, 0, WIDTH, HEIHT);

				data = toArray(
					context.getImageData(
						0, 0, WIDTH, HEIHT
					).data
				);
			}
			else {
				data = toArray(color.data);
			}
			
			data[3] = (data[3] / 255).toFixed(2) - 0;

			return data;
		},
		sort : function(color){
			///	<summary>
			///	将颜色的rgb颜色值进行排序，返回已排序的数组。
			///	</summary>
			///	<param name="color" type="String, ImageData">给定的颜色。</param>
			var data = this.getData(color);
			
			return [
				ColorNames.Red, ColorNames.Green, ColorNames.Blue
			].map(function(name, i){
				return {
					name : name,
					value : data[i]
				};
			}).sort(function(a, b){
				return b.value - a.value;
			});
		},
		toHex : function(color){
			///	<summary>
			///	将颜色转化为16进制格式。
			///	</summary>
			///	<param name="color" type="String, ImageData">给定的颜色。</param>
			var data = this.getData(color);

			return "#" + data.slice(
				0,
				3
			).map(function(val){
				var str = val.toString(16);

				return str.length === 1 ? "0" + str : str;
			}).join("");
		},
		toRgb : function(color){
			///	<summary>
			///	将颜色转化为rgb格式。
			///	</summary>
			///	<param name="color" type="String, ImageData">给定的颜色。</param>
			return rgbText.replace(
				this.getData(color)
			);
		},
		toRgba : function(color, _opacity){
			///	<summary>
			///	将颜色转化为rgba格式。
			///	</summary>
			///	<param name="color" type="String, ImageData">给定的颜色。</param>
			///	<param name="_opacity" type="Number">设定的透明度。</param>
			var data = this.getData(color);

			if(typeof _opacity === "number"){
				data[3] = _opacity;
			}

			return rgbaText.replace(data);
		}
	});

	return Color;
}(
	this.ColorNames,
	// WIDTH
	1,
	// HEIGHT
	1,
	// RGBA_REGX
	/rgba\s*\((.+)\)/,
	// ARGUMENTS_SPLIT_REGX
	/\s*,\s*/,
	// canvas
	new HTML('<canvas></canvas>').create()[0],
	// context
	null,
	// rgbText
	new Text("rgb({0}, {1}, {2})"),
	// rgbaText
	new Text("rgba({0}, {1}, {2}, {3})"),
	jQun.every,
	jQun.toArray
));

}.call(
	this,
	jQun.Text
));


// 颜色渐变相关
(function(Color, DraggableAreaList, ColorNames, pickEvent, searchcolorEvent){

this.GradientTypes = (function(){
	return new Enum(
		["Linear", "Radial"]
	);
}());

this.IColorGradient = (function(){
	return new Interface(
		["type", "search"],
		DraggableAreaList.prototype
	);
}());

this.ColorGradient = (function(IColorGradient){
	function ColorGradient(selector){
		///	<summary>
		///	颜色渐变区域。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var colorGradient = this, isPress = false, canvas = this.query(">canvas")[0];

		this.assign({
			canvas : canvas,
			context : canvas.getContext("2d")
		}).attach({
			dragging : function(e){
				colorGradient.pick.apply(
					colorGradient,
					e.point
				);
			}
		});
	};
	ColorGradient = new Class(ColorGradient, "jQun.ColorGradient", IColorGradient);

	ColorGradient.props({
		canvas : null,
		context : null,
		pick : function(left, top){
			///	<summary>
			///	选取指定位置的颜色。
			///	</summary>
			///	<param name="left" type="Number">距离区域内左上角的横轴距离。</param>
			///	<param name="top" type="Number">距离区域内左上角的纵轴距离。</param>
			var canvas = this.canvas;

			if(
				left === canvas.width
			){
				left -= 1;
			}

			if(
				top === canvas.height
			){
				top -= 1;
			}
			
			this.dispatch(
				pickEvent,
				{
					color : Color.toRgba(
						this.context.getImageData(left, top, 1, 1)
					),
					left : left,
					top : top,
					gradientType : this.type
				}
			)

			return this;
		}
	});

	return ColorGradient.constructor;
}(
	this.IColorGradient
));

this.RadialGradient = (function(ColorGradient, GradientTypes, WIDTH, HEIGHT, RGBA_REGEXP, round, isNaN){
	function RadialGradient(selector){
		///	<summary>
		///	放射性渐变(其实也是由3种线性渐变组合成的)类。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var radialGradient = this, buttonList = this.query(">button");

		this.assign({
			left : WIDTH - 1
		}).attach({
			pick : function(e){
				var left = e.left, top = e.top;

				radialGradient.left = left;
				radialGradient.top = top;

				buttonList.style = "left:" + left + "px;top:" + top + "px;";
			},
			searchcolor : function(e){
				radialGradient.reset(e.color);
			}
		});

		this.reset("red");
	};
	RadialGradient = new Class(RadialGradient, "jQun.RadialGradient", ColorGradient.prototype);

	RadialGradient.props({
		left : 0,
		reset : function(color){
			///	<summary>
			///	根据指定颜色重置该类。
			///	</summary>
			///	<param name="color" type="String, ImageData">指定的颜色。</param>
			var context = this.context;

			context.clearRect(0, 0, WIDTH, HEIGHT);

			// 渐变色之内，不包括初始值，所以都要预留1px的初始值颜色区
			[
				{ from : "#fff", to : "#fff", args : [0, 0, 0, HEIGHT] },
				{ from : color, to : Color.toRgba(color).match(RGBA_REGEXP)[0] + "0)", args : [WIDTH - 1, 0, 0, 0] },
				{ from : "rgba(0, 0, 0, 0)", to : "rgba(0, 0, 0, 1)", args : [0, 1, 0, HEIGHT - 2] }
			].forEach(function(item){
				var gradient = context["createLinearGradient"].apply(context, item.args);

				gradient.addColorStop(0, item.from);
				gradient.addColorStop(1, item.to);

				context.fillStyle = gradient;
				context.fillRect(0, 0, WIDTH, HEIGHT);
			});

			this.pick(this.left, this.top);

			return this;
		},
		search : function(color){
			///	<summary>
			///	根据指定颜色值搜索对应画布位置。
			///	</summary>
			///	<param name="color" type="String, ImageData">指定的颜色。</param>
			var left, top, sorted = Color.sort(color);

			top = (255 - sorted[0].value) / 255 * HEIGHT;;

			if(
				isNaN(top) || top < 0
			){
				top = 0;
			}

			left = (1 - (sorted[2].value / ((HEIGHT - top) / HEIGHT)) / 255) * WIDTH;

			if(
				isNaN(left) || left < 0
			){
				left = 0;
			}

			this.dispatch(
				searchcolorEvent,
				{
					color : Color.toHex({
						data : [
							ColorNames.Red, ColorNames.Green, ColorNames.Blue
						].map(function(name){
							var value = 0;

							sorted.every(function(dt, i){
								if(dt.name !== name)
									return true;

								if(i === 1){
									// 该点纵轴最大值，这里为了保持误差最小，所以top值不能用round计算过的。
									var max = dt.value / ((HEIGHT - top) / HEIGHT);

									// value应该为：根据纵轴最大值算出的横轴最小值
									value = round(max - (255 - max) / left * (WIDTH - left));

									// 当 0除以0的时候 值是NaN
									if(value < 0 || isNaN(value)){
										value = 0;
									}
									else if(value > 255){
										value = 255;
									}
								}
								else if(i === 0){
									value = 255;
								}

								return false;
							});
							
							return value;
						})
					}),
					gradientType : this.type
				}
			);

			top = round(top);
			left = round(left);
			
			this.dispatch(
				pickEvent,
				{
					top : top,
					left : left,
					color : color,
					gradientType : this.type
				}
			);

			return [left, top];
		},
		top : 0,
		type : GradientTypes.Radial
	});

	return RadialGradient.constructor;
}(
	this.ColorGradient,
	this.GradientTypes,
	// WIDTH
	160,
	// HEIGHT
	150,
	// RGBA_REGEXP
	/rgba\(\w+, \w+, \w+, /,
	Math.round,
	isNaN
));

this.LinearGradient = (function(ColorGradient, GradientTypes, WIDTH, HEIGHT, floor, fill){
	function LinearGradient(selector){
		///	<summary>
		///	线性颜色渐变类。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var buttonStyle = this.query(">button").style;
		
		this.attach({
			pick : function(e){
				buttonStyle.top = e.top + "px";
			},
			searchcolor : function(e){
				buttonStyle.top = e.top + "px";
			}
		});

		fill(WIDTH, HEIGHT, this.canvas, this.context);
	};
	LinearGradient = new Class(LinearGradient, "jQun.LinearGradient", ColorGradient.prototype);

	LinearGradient.props({
		search : function(color){
			///	<summary>
			///	根据指定颜色值搜索对应画布位置。
			///	</summary>
			///	<param name="color" type="String, ImageData">指定的颜色。</param>
			/*
				此渐变的查找规律是：忽略最低颜色值，即只关心第一高值与第二高值；
				第一高颜色值，决定主色调，第二高颜色值决定副色偏向。
				这样的方式比利用canvas的高去循环getImageData去对比，性能上有很大优势。
			*/
			var i, top, left = 0,

				// 渐变7种颜色，分为6份
				h = HEIGHT / 6, sorted = Color.sort(color);

			i = [
				// 可能出现的组合结果，顺序不能改变（第二高的值："01", "12", "20"是升值，"02", "10", "21"是降值）
				"01", "12", "20", "02", "10", "21"
			].indexOf(
				// 第一高的索引值 加上 第二高的索引值
				sorted[0].name.toString() + sorted[1].name.toString()
			);

			// [5, 3, 1, 1, 5, 3] 是对应“可能出现的组合结果”的颜色份值（6种颜色中的第几份）
			top = floor(
				[5, 3, 1, 1, 5, 3][i] * h + (25 - sorted[1].value / 255 * h) * (i > 2 ? -1 : 1)
			);

			// 如果这里用this.pick去取值，由于渐变区域小，很多渐变的细节颜色不存在，所以这里要模拟触发pick事件
			this.dispatch(
				searchcolorEvent,
				{
					top : top,
					left : left,
					color : Color.toHex({
						data : [
							ColorNames.Red, ColorNames.Green, ColorNames.Blue
						].map(function(name){
							var value = 0;

							sorted.every(function(dt, i){
								if(dt.name !== name)
									return true;

								if(i === 0){
									value = 255;
								}
								else if(i === 1){
									value = dt.value;
								}

								return false;
							});

							return value;
						})
					}),
					gradientType : this.type
				}
			);

			return [left, top];
		},
		type : GradientTypes.Linear
	});

	return LinearGradient.constructor;
}(
	this.ColorGradient,
	this.GradientTypes,
	// WIDTH
	28,
	// HEIGHT
	150,
	Math.floor,
	// fill
	function(WIDTH, HEIGHT, canvas, context){
		// 这里是因为渐变色之内，不包括初始值，所以要在上下各留1px的初始值色区
		var gradient = context.createLinearGradient(0, 1, 0, HEIGHT - 2);

		[
			"#f00", "#f0f", "#00f", "#0ff", "#0f0", "#ff0", "#f00"
		].forEach(function(color, i, colors){
			gradient.addColorStop(i / (colors.length - 1), color);
		});

		context.fillStyle = gradient;
		context.fillRect(0, 0, WIDTH, HEIGHT);
	}
));

}.call(
	this,
	this.Color,
	this.DraggableAreaList,
	this.ColorNames,
	// pickEvent
	new jQun.Event("pick"),
	// searchcolorEvent
	new jQun.Event("searchcolor")
));


// 颜色选择器的其他组件相关
(function(DraggableAreaList){

this.OpacityBar = (function(opacitychangedEvent){
	function OpacityBar(selector){
		///	<summary>
		///	透明度栏。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var draggableList, opacityBar = this;

		draggableList =  new DraggableAreaList(
				this.query(">button")[0]
			)
			.attach({
				dragging : function(e){
					var left = e.point[0], opacity = left / draggableList.width;

					if(opacity > 0 && opacity < 1){
						opacity = opacity.toFixed(2) - 0;
					}

					opacityBar.value = opacity;
				}
			});

		this.assign({
			draggableList : draggableList
		});
	};
	OpacityBar = new Class(OpacityBar, "jQun.OpacityBar", HTMLElementList.prototype);

	OpacityBar.override({
		value : {
			get : function(){
				return this.draggableList.get("title") - 0;
			},
			set : function(value){
				var draggableList = this.draggableList;

				draggableList.set(
					"title",
					value
				).setCSSPropertyValue(
					"left",
					value * 100 + "%"
				);

				this.dispatch(
					opacitychangedEvent,
					{ opacity : value }
				);
			}
		}
	}, { settable : true, gettable : true });

	OpacityBar.props({
		draggableList : null
	});

	return OpacityBar.constructor;
}(
	// opacitychangedEvent
	new Event("opacitychanged")
));

this.ColorInputArea = (function(){
	function ColorInputArea(selector){
		///	<summary>
		///	颜色输入区域类。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var colorInputArea = this;

		this.assign({
			inputList : this.query(">input"),
			spanStyle : this.query(">q>span").style
		});

		this.inputList.attach({
			keyup : function(e){
				if(e.keyCode !== 13)
					return;

				this.blur();
			},
			change : function(){
				colorInputArea.value = this.value;
			}
		});
	};
	ColorInputArea = new Class(ColorInputArea, "jQun.ColorInputArea", HTMLElementList.prototype);

	ColorInputArea.override({
		value : {
			set : function(value){
				///	<summary>
				///	设置颜色值。
				///	</summary>
				///	<param name="value" type="String">需要设定的颜色值。</param>
				this.spanStyle.backgroundColor = value;

				this.inputList.value = value;
			},
			get : function(){
				///	<summary>
				///	获取颜色值。
				///	</summary>
				return this.inputList.value;
			}
		}
	}, { settable : true, gettable : true });

	ColorInputArea.props({
		inputList : null,
		spanStyle : null
	});

	return ColorInputArea.constructor;
}());

}.call(
	this,
	this.DraggableAreaList
));


// 颜色选择器相关
(function(RadialGradient, LinearGradient, OpacityBar, ColorInputArea, Color, GradientTypes){

this.ColorPickerActions = (function(){
	return new Enum(
		["OK", "Cancel"]
	);
}());

this.ColorPicker = (function(PopupList, PopupListStatus, ColorPickerActions, html, actionEvent){
	function ColorPicker(){
		///	<summary>
		///	颜色选取器。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		var popupList, radialGradient, linearGradient, colorInputArea, opacityBar, colorPicker = this;

		this.combine(
			html.create()
		).assign({
			colorInputArea : new ColorInputArea(
				this.query("dd>blockquote")[0]
			),
			radialGradient : new RadialGradient(
				this.query("nav>p")[0]
			),
			linearGradient : new LinearGradient(
				this.query("nav>aside")[0]
			),
			opacityBar : new OpacityBar(
				this.query("dt")[0]
			)
		}).attach({
			change : function(e){
				var opacity = opacityBar.value;

				colorPicker.repaint(
					Color[opacity === 1 ? "toHex" : "toRgba"](e.target.value, opacity)
				);
			},
			pick : function(e){
				var color = e.color, opacity = opacityBar.value;

				colorInputArea.value = Color[opacity === 1 ? "toHex" : "toRgba"](color, opacity);

				if(e.gradientType !== GradientTypes.Linear)
					return;
				
				// 为了保持2种渐变的颜色一致
				radialGradient.reset(color);
			},
			searchcolor : function(e){
				if(e.gradientType !== GradientTypes.Radial)
					return;

				// 为了保持2种渐变的颜色一致
				linearGradient.search(e.color);
			},
			opacitychanged : function(e){
				var opacity = e.opacity;

				colorInputArea.value = Color[opacity === 1 ? "toHex" : "toRgba"](colorInputArea.value, opacity);
			},
			click : function(e, targetList){
				if(
					!targetList.isBtw("dd button", this)
				){
					return;
				}

				var action = targetList.getData("action") - 0;

				if(
					action === ColorPickerActions.OK
				){
					colorPicker.setColor(
						colorInputArea.value
					);
				}

				targetList.dispatch(
					actionEvent,
					{
						color : colorPicker.color,
						action : action
					}
				);

				popupList.hide();
			}
		});

		popupList = new PopupList(
			this.query(">figure"),
			this.query(">p>button")[0],
			true
		).attach({
			statuschanged : function(e){
				if(e.status === PopupListStatus.Show)
					return;

				colorPicker.reset(colorPicker.color);
			}
		});

		colorInputArea = this.colorInputArea;
		radialGradient = this.radialGradient;
		linearGradient = this.linearGradient;
		opacityBar = this.opacityBar;
	};
	ColorPicker = new Class(ColorPicker, "jQun.ColorPicker", HTMLElementList.prototype);

	ColorPicker.props({
		colorInputArea : null,
		color : "#ff0000",
		linearGradient : null,
		opacityBar : null,
		radialGradient : null,
		repaint : function(color){
			///	<summary>
			///	根据指定颜色重新绘制选取器。
			///	</summary>
			///	<param name="color" type="String, ImageData">指定的颜色。</param>
			var data = Color.getData(color);

			this.radialGradient.search(color);

			this.opacityBar.value = data[3];
			return this;
		},
		reset : function(color){
			///	<summary>
			///	根据指定的颜色重置选取器。
			///	</summary>
			///	<param name="color" type="String, ImageData">指定的颜色。</param>
			this.repaint(color);
			this.setColor(color);

			return this;
		},
		setColor : function(color){
			///	<summary>
			///	设置颜色值。
			///	</summary>
			///	<param name="color" type="String, ImageData">指定的颜色。</param>
			this.query(
				">p>button"
			).setCSSPropertyValue(
				"backgroundColor",
				color
			);

			this.color = color;

			return this;
		}
	});

	return ColorPicker.constructor;
}(
	jQun.PopupList,
	jQun.PopupListStatus,
	this.ColorPickerActions,
	// html
	new HTML([
		'<div class="colorPicker">',
			'<p data-border="black" data-bgcolor="white">',
				'<button></button>',
			'</p>',
			'<figure data-radius="small" data-border="gray">',
				'<nav data-display="inline-block-container">',
					'<p>',
						'<canvas width="160" height="150"></canvas>',
						'<button data-border="black" data-radius="big"></button>',
					'</p>',
					'<aside>',
						'<canvas width="28" height="150"></canvas>',
						'<button data-border="black"></button>',
					'</aside>',
				'</nav>',
				'<dl>',
					'<dt data-display="inline-block-container">',
						'<span>透明度：</span>',
						'<button title="1"></button>',
					'</dt>',				
					'<dd>',
						'<blockquote data-display="inline-block-container">',
							'<q data-border="gray">',
								'<span></span>',
							'</q>',
							'<input type="text" value="#ff0000" spellcheck="false" data-radius="small" />',
						'</blockquote>',
						'<p>',
							'<button data-action="0" data-border="gray">确定</button>',
							'<button data-action="1" data-border="gray">取消</button>',
						'</p>',
					'</dd>',
				'</dl>',
			'</figure>',
		'</div>'
	].join("")),
	// actionEvent
	new Event("action")
));

}.call(
	this,
	this.RadialGradient,
	this.LinearGradient,
	this.OpacityBar,
	this.ColorInputArea,
	this.Color,
	this.GradientTypes
));


// 可编辑的内容区域相关
(function(Text){

this.ContentArea = (function(HTMLUnknownElement, html, cursortargetchangeEvent){
	function ContentArea(_disableEditable){
		///	<summary>
		///	编辑器内容区域类。
		///	</summary>
		///	<param name="_disableEditable" type="Boolean">内容是否可编辑。</param>
		var contentArea = this, oldTarget = null;

		this.combine(
				html.create()
			)
			.set(
				"contentEditable",
				!_disableEditable
			)
			.attach({
				keydown : function(e){
					switch(
						e.keyCode
					){
						case 9 :
							contentArea.insertSelectionText("\t");
							break;

						case 13 :
							contentArea.newLine();
							break;

						default :
							return;
					}

					e.preventDefault();
					e.stopPropagation();
				},
				reposcursor : function(e){
					var range = e.range, node = range.startContainer;

					contentArea.range = range.cloneRange();
				
					if(
						node instanceof Text
					){
						node = node.parentNode;
					}

					if(
						node === oldTarget
					){
						return;
					}

					oldTarget = node;

					cursortargetchangeEvent.trigger(node);
				}
			});

		this.newLine(true);
	};
	ContentArea = new Class(ContentArea, "jQun.ContentArea", HTMLElementList.prototype);

	ContentArea.props({
		getContent : function(){
			///	<summary>
			///	获取内容字符串。
			///	</summary>
			return this.innerHTML;
		},
		getSelectionText : function(){
			///	<summary>
			///	获取当前选中文本的字符串。
			///	</summary>
			return this.range.toString();
		},
		getTextContent : function(){
			///	<summary>
			///	获取纯文本内容。
			///	</summary>
			return this.get("textContent");
		},
		insertSelectionText : function(text){
			///	<summary>
			///	在当前光标处插入文本。
			///	</summary>
			///	<param name="text" type="String">需要插入的文本值。</param>
			var range = this.restoreRange();

			// 必须得先判断再插入节点
			if(range.collapsed){
				var node = range.startContainer;

				// 这里为了保持节点的存在，所以分了2步考虑，如果不这样做，那么如果是空节点，会被删除的
				if(
					node instanceof Text
				){
					var start = range.startOffset, offset = start + text.length;

					node.insertData(start, text);
					this.setSelection(node, offset, node, offset);

					return this;
				}
			}

			var textNode = document.createTextNode(text);

			range.insertNode(textNode);
			this.setSelection(textNode, text.length, textNode, text.length);

			return this;
		},
		isUploaded : function(){
			///	<summary>
			///	判断编辑器内是否有未上传完成的图像。
			///	</summary>
			return this.query('img[src^="data:image/"]').length === 0;
		},
		newLine : function(_isEnding){
			///	<summary>
			///	在当前光标处创建新的一行。
			///	</summary>
			///	<param name="_isEnding" type="Boolean">是否为结束标识。</param>
			var brList = this.replaceSelectionWithElement(
						"br",
						"",
						!_isEnding
					)
					.set(
						"textContent",
						_isEnding ? "\f" : LINE_BREAK
					);

			if(
				_isEnding
			){
				this.setSelection(brList[0], -1, brList[0], -1);
			}

			return brList;
		},
		range : null,
		replaceSelectionText : function(text){
			///	<summary>
			///	替换当前选中的文本。
			///	</summary>
			///	<param name="text" type="String">需要替换的文本值。</param>
			var range = this.restoreRange(), textNode = document.createTextNode(text);

			range.insertNode(textNode);
			range.setStartAfter(textNode);
			range.deleteContents();
			
			this.restoreRange(range);

			return this;
		},
		replaceSelectionWithElement : function(tag, _htmlString, _shouldSetAfter){
			///	<summary>
			///	根据指定的标签名生成元素替换选中区域。
			///	</summary>
			///	<param name="tag" type="String">元素标签名。</param>
			///	<param name="_htmlString" type="String">元素的新内容。</param>
			///	<param name="_shouldSetAfter" type="Boolean">是否将光标设置到替换的元素之后。</param>
			var list = new HTMLElementList(), elem = document.createElement(tag);

			if(
				elem instanceof HTMLUnknownElement
			){
				return list;
			}

			var range = this.restoreRange();

			list.push(elem);

			range.insertNode(elem);
			range.setStartAfter(elem);

			// 插入新文本
			if(range.collapsed){
				/*
					按道理应该使用textContent属性，但是为了保护image标签要考虑以下几点：
					1. image的textContent可以赋值的，image的textContent应为空字符串
					2. 退而求其次，应该用innerText，但是image这种封闭式标签，使用会报错，这也就是innerText与textContent的区别
					3. 所以，只剩下innerHTML，封闭式标签的innerHTML始终为空字符串。
				*/
				elem.innerHTML = typeof _htmlString === "string" ? _htmlString : "&nbsp;";

				range.deleteContents();
			}
			// 替换文本
			else {
				if(
					typeof _htmlString === "string"
				){
					elem.innerHTML = _htmlString;

					range.deleteContents();
				}
				else {
					/*
						这里不用innerHTML属性，是因为使用innerHTML会丢失很多标签信息，如image、input和带样式的标签等。
					*/
					elem.appendChild(
						range.extractContents()
					);
				}
			}

			if(
				_shouldSetAfter
			){
				this.setSelection(elem);
			}
			else {
				var range = document.createRange();

				range.selectNodeContents(elem);
				this.restoreRange(range);
			}

			return list;
		},
		restoreRange : function(_range){
			///	<summary>
			///	恢复编辑器内的选中区域（如果给定range，则恢复给定range，否则恢复上次自动记录的range）。
			///	</summary>
			///	<param name="_range" type="Range">需要恢复的区域range。</param>
			var range = _range || this.range;

			/*
				如果不存在，需要初始化，初始化不能在构造函数里，
				因为编辑器未添加到文档中，IE会把range的始终节点设置为document，
				然后再恢复此range的时候会报错。
			*/
			if(
				!range
			){
				range = document.createRange();

				range.setStart(this[0], 0);
				range.setEnd(this[0], 0);
			}

			this.range = range;

			selection.removeAllRanges();

			try{
				// IE在元素没有添加到文档之前，添加range会报错
				selection.addRange(range);
			}
			catch(e){}
			
			this[0].normalize();

			return range;
		},
		setContent : function(content){
			///	<summary>
			///	设置编辑器内容。
			///	</summary>
			///	<param name="content" type="String">内容字符串。</param>
			this.innerHTML = content;

			return this;
		},
		setSelection : function(startNode, _startOffset, _endNode, _endOffset){
			///	<summary>
			///	设置选中区域。
			///	</summary>
			///	<param name="startNode" type="Node">选中区域起始节点。</param>
			///	<param name="_startOffset" type="Number">选中区域起始节点的偏移量。</param>
			///	<param name="_endNode" type="Node">选中区域终止节点。</param>
			///	<param name="_endOffset" type="Number">选中区域终止节点的偏移量。</param>
			var range = document.createRange();

			if(
				this.contains(startNode)
			){
				range[
					_startOffset == null ?
						"setStartAfter" :
						_startOffset === -1 ? 
							"setStartBefore" :
							"setStart"
				](startNode, _startOffset);
			}
			
			if(
				!_endNode
			){
				_endNode = startNode;
			}

			if(
				this.contains(_endNode)
			){
				range[
					_endOffset == null ?
						"setEndAfter" :
						_endOffset === -1 ?
							"setEndBefore" :
							"setEnd"
				](_endNode, _endOffset);
			}

			this.restoreRange(range);
			return this;
		},
		setTextContent : function(content){
			///	<summary>
			///	设置编辑器文本内容。
			///	</summary>
			///	<param name="content" type="String">内容字符串。</param>
			this.set("textContent", content);
			return this;
		}
	});

	return ContentArea.constructor;
}(
	window.HTMLUnknownElement || function HTMLUnknownElement(){},
	// html
	new HTML(
		'<div class="contentArea" contenteditable spellcheck="false" data-bgcolor="white"></div>'
	),
	// cursortargetchangeEvent
	new Event("cursortargetchange")
));

}.call(
	this,
	Text
));


// 与代码编辑器相关
(function(ContentArea, List, Text, RegExp, descriptorHtml){

this.SpecialRegExp = (function(){
	function SpecialRegExp(source, _descriptor){
		///	<summary>
		///	特殊的正则表达式。
		///	</summary>
		///	<param name="source" type="String">正则表达式字符串。</param>
		///	<param name="_descriptor" type="String">相关描述符。</param>
		this.assign({
			descriptor : _descriptor,
			source : source
		});
	};
	SpecialRegExp = new Class(SpecialRegExp, "jQun.SpecialRegExp");

	SpecialRegExp.props({
		descriptor : "",
		source : RegExp.prototype.source,
		test : function(string){
			///	<summary>
			///	用指定字符串来测试该特殊的正则表达式。
			///	</summary>
			///	<param name="string" type="String">指定的测试字符串。</param>
			return this.valueOf().test(string);
		},
		valueOf : function(){
			///	<summary>
			///	获取生成的正则表达式。
			///	</summary>
			return new RegExp(this.source, "g");
		}
	});

	return SpecialRegExp.constructor;
}());

this.CommonSpecialRegExps = (function(SpecialRegExp, closeRegExpText){
	function CommonSpecialRegExps(){
		this.assign({
			comment : new SpecialRegExp(
				"//[\\s\\S]*$",
				"comment"
			),
			func : new SpecialRegExp(
				"[_$\\w]+[_$\\w\\d]*(?=\\s*\\()",
				"func"
			),
			keyword : this.createkeyword(
				[
					// A - I(A B C D E F G I)
					"break", "case", "continue", "default", "do", "else", "false", "for",  "if", "in",
					// J - Q(J K L M N O P Q)
					"new", "null",
					// R - Z(R S T U V W X Y Z)
					"return", "switch", "this", "true", "while"
				]
			),
			number : new SpecialRegExp(
				"\\b\\d+(?:(?:\\.)\\d+)?\\b",
				"number"
			),
			operator : new SpecialRegExp(
				// 加 减 乘 除 等于 小于 大于 百分号 且 或 非
				"[\\+\\-\\*/=\\<\\>%&\\|\\!]",
				"operator"
			),
			regExp : this.createClose("/", "regExp", "+"),
			string : new SpecialRegExp(
				[
					"(?:",
					this.combineRegExpSources([
						this.createClose('"', "", "*").source,
						this.createClose("'", "", "*").source
					]),
					")"
				].join(""),
				"string"
			),
			tab : new SpecialRegExp(
				"\t",
				"tab"
			)	
		});
	};
	CommonSpecialRegExps = new StaticClass(
		CommonSpecialRegExps,
		"jQun.CommonSpecialRegExps",
		{
			combineRegExpSources : function(sources){
				return [
					"(?:",
					sources.join(")|(?:"),
					")"
				].join("");
			},
			comment : null,
			createClose : function(delimiter, _descriptor, _length){
				return new SpecialRegExp(
					closeRegExpText.replace({
						delimiter : delimiter,					
						length : typeof _length === "undefined" ? "*" : _length
					}),
					_descriptor
				);
			},
			createkeyword : function(keyword, _descriptor){
				return new SpecialRegExp(
					[
						"\\b(?:",
						keyword.join("|"),
						")\\b"
					].join(""),
					_descriptor || "keyword"
				);
			},
			func : null,
			keyword : null,
			number : null,
			operator : null,
			regExp : null,
			string : null,
			tab : null
		}
	);

	return CommonSpecialRegExps;
}(
	this.SpecialRegExp,
	// closeRegExpText
	new Text('{delimiter}(?:(?:\\\\{delimiter})|(?:[^{delimiter}])){length}{delimiter}')
));

this.SpecialRegExps = (function(SpecialRegExp, CommonSpecialRegExps){
	function SpecialRegExps(list){
		// 对顺序有一定要求
		this.push(
			CommonSpecialRegExps.tab,
			CommonSpecialRegExps.comment,
			CommonSpecialRegExps.regExp,
			CommonSpecialRegExps.string,
			CommonSpecialRegExps.keyword,
			CommonSpecialRegExps.func,
			CommonSpecialRegExps.number,
			CommonSpecialRegExps.operator
		);

		this.combination = new RegExp(
			CommonSpecialRegExps.combineRegExpSources(
				this.map(function(specialRegExp){
					return specialRegExp.source + "()";
				})
			),
			"g"
		);
	};
	SpecialRegExps = new Class(SpecialRegExps, "jQun.SpecialRegExps", List.prototype);

	SpecialRegExps.props({
		combination : null
	});

	return SpecialRegExps.constructor;
}(
	this.SpecialRegExp,
	this.CommonSpecialRegExps
));

this.ICodeBlock = (function(){
	return new Interface(
		["name"],
		HTMLElementList.prototype
	);
}());

this.CodeBlock = (function(ICodeBlock){
	function CodeBlock(_selector){
		///	<summary>
		///	代码块区域。
		///	</summary>
		///	<param name="_selector" type="String">元素选择器。</param>
		this.className = this.name;
	};
	CodeBlock = new Class(CodeBlock, "jQun.CodeBlock", ICodeBlock);

	return CodeBlock.constructor;
}(
	this.ICodeBlock
));

this.CodeLine = (function(CodeBlock, SpecialRegExps, html, forEach, lastIndexOf, getDescriptorElement){
	function CodeLine(content, _specialRegExps, _editable){
		///	<summary>
		///	代码行区域。
		///	</summary>
		///	<param name="content" type="String">代码内容。</param>
		///	<param name="_specialRegExps" type="SpecialRegExps">特殊的正则类。</param>
		///	<param name="_editable" type="Boolean">是否可编辑。</param>
		this.assign(
				{ specialRegExps : _specialRegExps }
			)
			.combine(
				html.create(null, true)
			)
			.reset(
				content
			);

		if(
			!_editable
		){
			return;
		}

		var codeLine = this;

		this.attach({
			reposcursor : function(){
				var lines = this.textContent.match(/.*\r\n(?!$)/g);

				// 根据换行符创建新的行
				if(
					lines
				){
					for(
						var i = 0,
							j = lines.length;
						i < j;
						i++
					){
						var range = document.createRange();

						range.setStart(this, 0);
						range.setEndAfter(
							codeLine.query("br")[0]
						);
						range.deleteContents();

						// 新创建的行都添加到当前行之前
						new codeLine.constructor(lines[i]).insertBefore(this);
					}
				}
			}
		});
	};
	CodeLine = new Class(CodeLine, "jQun.CodeLine", CodeBlock.prototype);

	CodeLine.props({
		name : "codeLine",
		reset : function(content){
			///	<summary>
			///	重置代码内容。
			///	</summary>
			///	<param name="content" type="String">代码内容。</param>
			var children = this.children, end = 0,
			
				specialRegExps = this.specialRegExps;

			this.innerHTML = "";

			if(
				specialRegExps.combination
			){
				content.replace(
					specialRegExps.combination,
					function(subContent){
						var index = arguments[arguments.length - 2];

						children
							.append(
								document.createTextNode(
									content.substring(end, index)
								)
							)
							.append(
								getDescriptorElement(
									specialRegExps[lastIndexOf.call(arguments, "") - 1].descriptor,
									subContent
								)
							);

						end = index + subContent.length;
						return "";
					}
				);
			}

			children.append(
				document.createTextNode(
					content.substring(end)
				)
			);

			return this;
		},
		specialRegExps : new SpecialRegExps()
	});

	return CodeLine.constructor;
}(
	this.CodeBlock,
	this.SpecialRegExps,
	// html
	new HTML('<p class="codeLine"></p>'),
	jQun.forEach,
	Array.prototype.lastIndexOf,
	// getDescriptorElement
	function(descriptor, subContent){
		var element = descriptorHtml.create(
			{ descriptor : descriptor },
			true
		)[0];

		element.textContent = subContent;

		return element;
	}
));

this.CodeArea = (function(CodeLine, newLine, forEach, toFormatLine){
	function CodeArea(_contentEditable, _BindingCodeLine){
		///	<summary>
		///	代码区域。
		///	</summary>
		///	<param name="_contentEditable" type="Boolean">是否可写。</param>
		///	<param name="_BindingCodeLine" type="CodeLine">需要绑定的代码行类。</param>
		var codeArea = this;

		this.assign(
				{ BindingCodeLine : _BindingCodeLine }
			)
			.setData(
				"color",
				"white"
			)
			.removeData(
				"bgcolor"
			)
			.set(
				"contentEditable",
				!!_contentEditable
			)
			.setContent(
				""
			);

		this.classList.add("codeArea");

		if(
			!_contentEditable
		){
			return;
		}

		this.BoundCodeLine = EditableCodeLine;

		this.attach({
			paste : function(e){
				codeArea.replaceSelectionText(
					e.clipboardData.getData("Text")
				);

				toFormatLine(codeArea, true);
				e.preventDefault();
			},
			reposcursor : function(){
				//toFormatLine(codeArea);
			}
		});
	};
	CodeArea = new Class(CodeArea, "jQun.CodeArea", ContentArea.prototype);

	CodeArea.override({
		/*
		newLine : function(_text, _lineNode, _shouldInsertBefore, _shouldKeepSelection){
			///	<summary>
			///	创建新的一行。
			///	</summary>
			///	<param name="_text" type="String">新的一行的文本值</param>
			///	<param name="_lineNode" type="Node">指定的节点，新行将插入该节点“之后”或“之前”</param>
			///	<param name="_shouldInsertBefore" type="Boolean">是否将新行插入指定节点之前</param>
			///	<param name="_shouldKeepSelection" type="Boolean">编辑状态下，是否保持当前的selection</param>
			var codeLine = new this.BoundCodeLine(typeof _text === "string" ? _text : "");

			newLine.call(this);return;

			// 如果指定插入点的行节点存在
			if(
				_lineNode
			){
				// 插入到指定行节点 “之前” 或 “之后”
				codeLine[_shouldInsertBefore ? "insertBefore" : "insertAfter"](_lineNode);
			}
			else {
				// 如果行节点不存在，默认插入到编辑器最后一行
				codeLine.appendTo(this[0]);
			}
			
			// 如果是编辑状态下
			if(
				codeLine instanceof EditableCodeLine
			){
				// 是否保持当前的selection
				if(
					!_shouldKeepSelection
				){
					var editableArea = codeLine.editableAreaList[0];

					// 如果不需要保持，那么聚焦当前行
					this.setSelection(editableArea, 0, editableArea, 0);
				}
			}

			return codeLine;
		},
		*/
		setContent : function(content){
			///	<summary>
			///	设置代码内容。
			///	</summary>
			///	<param name="content" type="String">需要设置的代码内容。</param>
			var codeAreaElement = this[0], BindingCodeLine = this.BindingCodeLine;

			this.innerHTML = "";

			forEach(
				content instanceof Array ? content : content.match(/.*?\r\n/g),
				function(result){
					content = content.substring(result.length);

					new BindingCodeLine(
							result
						)
						.appendTo(
							codeAreaElement
						);
				}
			);

			new BindingCodeLine(
					content
				)
				.appendTo(
					codeAreaElement
				);

			return this;
		}
	});

	CodeArea.props({
		BindingCodeLine : CodeLine,
		formatLine : function(lineNode){
			return;

			if(
				lineNode.parentNode !== this[0]
			){
				return this;
			}

			var formatter = this.formatter,

				lines = lineNode.textContent.split(LINE_BREAK), length = lines.length;

			// 根据换行符创建新的行
			if(
				length > 1
			){
				var i = 0, range = this.range.cloneRange(), lineNodeList = new HTMLElementList(lineNode);

				// 创建的数量为总行数-1，因为最后一行是已经存在的当前行
				for(
					var j = length - 1;i < j;i++
				){
					// 新创建的行都添加到当前行之前
					this.newLine(lines[i], lineNode, true, true);
				}
				
				// 如果结束光标在当前行
				if(
					lineNodeList.contains(range.startContainer) || lineNodeList.contains(range.endContainer)
				){
					var lastLine = lines[i], textNode = document.createTextNode("");

					// 删除多余文本
					range.setStart(lineNode, 0);
					range.deleteContents();

					textNode.textContent = lastLine.substring(0, lastLine.length - lineNode.textContent.length);
				
					// 插入文本节点
					range.insertNode(textNode);
					this.setSelection(textNode);
				}
				else {
					lineNode.textContent = lines[i];
				}
			}

			//new CodeLine(lineNode);

			return this;
		}
	});

	return CodeArea.constructor;
}(
	this.CodeLine,
	this.ContentArea.prototype.newLine,
	jQun.forEach,
	// toFormatLine
	function(codeArea, _shouldScrollIntoView){
		var range = codeArea.range, sc = range.startContainer, ec = range.endContainer;

		(
			sc === ec ? [sc] : [sc, ec]
		).forEach(
			function(node){
				var lineList;

				lineList = new HTMLElementList(
					node
				)
				.btw(
					">*",
					this
				);

				codeArea.formatLine(lineList.length > 0 ? lineList[0] : node);
			},
			codeArea[0]	
		);

		if(
			_shouldScrollIntoView
		){
			ec.scrollIntoView();
		}
	}
));

}.call(
	this,
	this.ContentArea,
	jQun.List,
	jQun.Text,
	RegExp,
	// descriptorHtml
	new HTML('<span data-descriptor="{descriptor}"></span>')
));


// 继承CodeArea并具有指定语言解析能力的编辑器
(function(CodeArea, CodeLine, SpecialRegExps, CommonSpecialRegExps){

this.JavaScriptCodeLine = (function(){
	function JavaScriptCodeLine(content){
		///	<summary>
		///	JavaScript代码行区域。
		///	</summary>
	};
	JavaScriptCodeLine = new Class(JavaScriptCodeLine, "jQun.JavaScriptCodeLine", CodeLine.prototype);

	JavaScriptCodeLine.override({
		specialRegExps : new SpecialRegExps([
			CommonSpecialRegExps.createkeyword([
				"function", "var", "try", "catch", "finally", "instanceof", "typeof", "void", "debugger", "with", "delete"
			])
		])
	});

	return JavaScriptCodeLine.constructor;
}());

this.JavaScriptCodeArea = (function(JavaScriptCodeLine){
	function JavaScriptCodeArea(){
		///	<summary>
		///	JavaScript代码区域。
		///	</summary>
		this.classList.add("javaScriptCodeArea");
	};
	JavaScriptCodeArea = new Class(JavaScriptCodeArea, "jQun.JavaScriptCodeArea", CodeArea.prototype);

	JavaScriptCodeArea.override({
		BindingCodeLine : JavaScriptCodeLine
	});

	return JavaScriptCodeArea.constructor;
}(
	this.JavaScriptCodeLine
));

}.call(
	this,
	this.CodeArea,
	this.CodeLine,
	this.SpecialRegExps,
	this.CommonSpecialRegExps
));


// 下拉分组相关
(function(ToolBarGroup, SearchableList){

this.FontFamilies = (function(){
	return new Enum(
		["Aria", "宋体", "黑体", "微软雅黑"]
	);
}());

this.FontSizes = (function(){
	return new Enum(
		["12px", "14px", "16px", "18px", "24px", "36px", "48px"]
	);
}());

this.FontFamilyList = (function(FontFamilies, toArray){
	function FontFamilyList(){
		///	<summary>
		///	字体下拉列表类。
		///	</summary>
		var fontFamilyList = this, options = this.options;

		this.attach({
			selectoption : function(e){
				fontFamilyList.query(
					"input"
				).setCSSPropertyValue(
					"fontFamily", e.text
				);
			}
		});

		this.classList.add("fontFamilyList");

		options.append(FontFamilies);
		options.select(FontFamilies["宋体"]);

		FontFamilies.forEach(
			function(i, family){
				this.query(
						'li[data-key="' + i + '"]'
					)
					.setCSSPropertyValue(
						"fontFamily",
						family
					);
			},
			this
		);
	};
	FontFamilyList = new Class(FontFamilyList, "jQun.FontFamilyList", SearchableList.prototype);

	return FontFamilyList.constructor;
}(
	this.FontFamilies,
	jQun.toArray
));

this.FontSizeList = (function(FontSizes){
	function FontSizeList(){
		///	<summary>
		///	字体大小下拉列表类。
		///	</summary>
		this.classList.add("fontSizeList");

		this.options.append(FontSizes);
	};
	FontSizeList = new Class(FontSizeList, "jQun.FontSizeList", SearchableList.prototype);

	return FontSizeList.constructor;
}(
	this.FontSizes
));

this.GroupByDropDownList = (function(FontFamilyList, FontSizeList, FontFamilies, FontSizes){
	function GroupByDropDownList(selector, contentArea){
		///	<summary>
		///	下拉框分组。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器</param>
		///	<param name="contentArea" type="ContentArea">编辑器内容区域</param>
		var fontFamilyList = new FontFamilyList(), fontSizeList = new FontSizeList();
			
		contentArea.attach({
			cursortargetchange : function(e, targetList){
				fontFamilyList
					.options
					.select(
						FontFamilies[
							targetList.getCSSPropertyValue(
								"fontFamily"
							).split(
								","
							)[0]
						],
						"cursortargetchange"
					);

				fontSizeList
					.options
					.select(
						FontSizes[
							targetList.getCSSPropertyValue("fontSize")
						],
						"cursortargetchange"
					);
			}
		});

		fontFamilyList
			.options
			.select(
				FontFamilies["宋体"]
			);

		fontFamilyList.attach({
			selectoption : function(e){
				if(e.selectType === "cursortargetchange")
					return;

				contentArea
					.replaceSelectionWithElement(
						"span"
					)
					.setCSSPropertyValue(
						"fontFamily",
						e.text
					);
			}
		});

		fontSizeList.attach({
			selectoption : function(e){
				if(e.selectType === "cursortargetchange")
					return;

				var text = e.text;

				contentArea
					.replaceSelectionWithElement(
						"span"
					)
					.setCSSPropertyValue(
						"fontSize",
						text
					)
					.setCSSPropertyValue(
						"lineHeight",
						"1.5em"
					);
			}
		});

		this.children
			.append(
				fontFamilyList[0]
			)
			.append(
				fontSizeList[0]
			);
	};
	GroupByDropDownList = new Class(GroupByDropDownList, "jQun.GroupByDropDownList", ToolBarGroup.prototype);

	return GroupByDropDownList.constructor;
}(
	this.FontFamilyList,
	this.FontSizeList,
	this.FontFamilies,
	this.FontSizes
));

}.call(
	this,
	this.ToolBarGroup,
	jQun.SearchableList
));


// 颜色分组相关
(function(ToolBarGroup, ColorPicker, ColorPickerActions){

this.GroupByColor = (function(){
	function GroupByColor(selector, contentArea){
		///	<summary>
		///	颜色分组。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器</param>
		///	<param name="contentArea" type="ContentArea">编辑器内容区域</param>
		var colorPick = new ColorPicker();

		contentArea.attach({
			cursortargetchange : function(e, targetList){
				colorPick.reset(
					targetList.getCSSPropertyValue("color")
				);
			}
		});

		colorPick.attach({
			action : function(e){
				if(e.action !== ColorPickerActions.OK)
					return;

				contentArea
					.replaceSelectionWithElement(
						"span"
					)
					.setCSSPropertyValue(
						"color",
						e.color
					)
					.query(
						"*"
					)
					.setCSSPropertyValue(
						"color",
						"inherit"
					);
			}
		}).reset(
			"#000"
		);

		this.children.append(colorPick[0]);
	};
	GroupByColor = new Class(GroupByColor, "jQun.GroupByColor", ToolBarGroup.prototype);

	return GroupByColor.constructor;
}());

}.call(
	this,
	this.ToolBarGroup,
	this.ColorPicker,
	this.ColorPickerActions
));


// 字体外观分组相关
(function(ToolBarGroup){

this.FontAppearanceCategories = (function(){
	return new Enum(
		["Weight", "Style", "Decoration", "Highlight"]
	);
}());

this.FontAppearance = (function(FontAppearanceCategories, html, fontstyleclickedEvent, info, set){
	function FontAppearance(){
		///	<summary>
		///	字体样式类。
		///	</summary>
		this.combine(
				html.create()
			)
			.attach({
				click : function(e, targetList){
					if(
						!targetList.isBtw(">button", this)
					){
						return;
					}

					var dataset = targetList.dataset, category = dataset.get("category") - 0;

					dataset.toggle("focus");

					targetList.dispatch(
						fontstyleclickedEvent,
						set(
							{ category : category },
							info[
								FontAppearanceCategories.getNameByValue(category, true)
							][
								dataset.contains("focus") ? 1 : 0
							]
						)
					);
				}
			});
	};
	FontAppearance = new Class(FontAppearance, "jQun.FontAppearance", HTMLElementList.prototype);

	FontAppearance.override({
		blur : function(){
			///	<summary>
			///	使所有按钮失去焦点。
			///	</summary>
			this.query(
				'button[data-focus]'
			).removeData(
				"focus"
			);

			return this;
		},
		focus : function(fontAppearanceCategories){
			///	<summary>
			///	聚焦指定分类的按钮。
			///	</summary>
			///	<param name="category" type="FontAppearanceCategories">指定的分类。</param>
			this.query(
				'button[data-category="' + fontAppearanceCategories + '"]'
			).setData(
				"focus"
			);

			return this;
		}
	});

	return FontAppearance.constructor;
}(
	this.FontAppearanceCategories,
	// html
	new HTML([
		'<div class="fontAppearance" data-color="white">',
			'@for([',
				'{ text : "B", title : "粗体" },',
				'{ text : "I", title : "斜体" },',
				'{ text : "U", title : "下划线" },',
				'{ text : "M", title : "标记" }',
			'] ->> button, category){',
				'<button title="{button.title}" data-category="{category}">{button.text}</button>',
			'}',
		'</div>'
	].join("")),
	// fontstyleclickedEvent
	new Event("fontstyleclicked"),
	// info
	{
		weight : [
			{ tag : "span", style : "font-weight:lighter;" },
			{ tag : "strong", style : "" }
		],
		style : [
			{ tag : "span", style : "font-style:normal;" },
			{ tag : "i", style : "" }
		],
		decoration : [
			{ tag : "unknow", style : "" },
			{ tag : "ins", style : "" }
		],
		highlight : [
			{ tag : "unknow", style : "" },
			{ tag : "mark", style : "" }
		]
	},
	jQun.set
));

this.GroupByFontAppearance = (function(FontAppearance, FontAppearanceCategories){
	function GroupByFontAppearance(selector, contentArea){
		///	<summary>
		///	字体外观分组。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器</param>
		///	<param name="contentArea" type="ContentArea">编辑器内容区域</param>
		var fontAppearance = new FontAppearance(contentArea);

		contentArea.attach({
			cursortargetchange : function(e, targetList){
				var value = targetList.getCSSPropertyValue("fontWeight");

				fontAppearance.blur();
				
				if(
					value === "bold" || value - 0 > 400
				){
					fontAppearance.focus(FontAppearanceCategories.Weight);
				}

				value = targetList.getCSSPropertyValue("fontStyle");

				if(
					value === "italic"
				){
					fontAppearance.focus(FontAppearanceCategories.Style);
				}
			}
		});

		fontAppearance.attach({
			fontstyleclicked : function(e){
				var elemList = contentArea.replaceSelectionWithElement(e.tag, contentArea.getSelectionText());

				if(
					e.style === ""
				){
					return;
				}

				elemList.style = e.style;
			}
		});

		this.children.append(fontAppearance[0]);
	};
	GroupByFontAppearance = new Class(GroupByFontAppearance, "jQun.GroupByFontAppearance", ToolBarGroup.prototype);

	return GroupByFontAppearance.constructor;
}(
	this.FontAppearance,
	this.FontAppearanceCategories
));

}.call(
	this,
	this.ToolBarGroup
));


// 对齐工具分组相关
(function(ToolBarGroup, SimpleButton){

this.GroupByAlign = (function(click){
	function GroupByAlign(selector, contentArea){
		///	<summary>
		///	对齐工具分组。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器</param>
		///	<param name="contentArea" type="ContentArea">编辑器内容区域</param>
		this.children.append(
			new SimpleButton(
				"left",
				"文本左对齐"
			).attach({
				buttonclicked : function(e){
					click(e, contentArea);
				}
			})[0]
		).append(
			new SimpleButton(
				"center",
				"文本居中对齐"
			).attach({
				buttonclicked : function(e){
					click(e, contentArea);
				}
			})[0]
		).append(
			new SimpleButton(
				"right",
				"文本右对齐"
			).attach({
				buttonclicked : function(e){
					click(e, contentArea);
				}
			})[0]
		);
	};
	GroupByAlign = new Class(GroupByAlign, "jQun.GroupByAlign", ToolBarGroup.prototype);

	return GroupByAlign.constructor;
}(
	// click
	function(e, contentArea){
		contentArea.replaceSelectionWithElement(
			"p"
		).setCSSPropertyValue(
			"textAlign",
			e.action
		);
	}
));

}.call(
	this,
	this.ToolBarGroup,
	this.SimpleButton
));


// 插入分组相关
(function(ToolBarGroup, ImageFileInput, SimpleButton, File, canvas, round){

this.GroupByInsert = (function(anchorHtml, open, fillDataByProgress){
	function GroupByInsert(selector, contentArea, _requestConnection){
		///	<summary>
		///	插入分组。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器</param>
		///	<param name="contentArea" type="ContentArea">编辑器内容区域</param>
		///	<param name="_requestConnection" type="RequestConnection">上传图片使用的请求链接</param>
		var groupByInsert = this;

		this.children
			.append(
				new SimpleButton(
					"insertAnchor",
					"将所选区域转化为超链接"
				).attach({
					buttonclicked : function(){
						contentArea.replaceSelectionWithElement(
							"span",
							anchorHtml.render(
								{ href : contentArea.getSelectionText() }
							),
							true
						);
					}
				})[0]
			).append(
				new ImageFileInput(
					"插入图片"
				)
				.attach({
					fileselected : function(e){
						var imageList, file = e.file;
						
						imageList = contentArea
									.replaceSelectionWithElement(
										"img",
										"",
										true
									)
									.setCSSPropertyValue(
										"margin",
										"10px 0"
									)
									.setCSSPropertyValue(
										"maxWidth",
										"100%"
									);

						// 将文件转化为base64数据
						File.toData(
							file,
							function(data){
								// 如果不存在请求链接，那么表示图像要以base64保存在内容中
								if(
									!_requestConnection
								){
									imageList.set("src", data);
									return;
								}

								// 创建另外一个缓存图像，用于储存base64原图
								var img = new Image();

								// 图片首次加载监听
								img.addEventListener(
									"load",
									function(){
										var image = this;

										// 因为只要监听首次，移除此监听
										this.removeEventListener("load", arguments.callee);
										
										// 上传文件
										_requestConnection.upload(
											file,
											// 上传完成回调函数
											function(data){
												var src = data.fileSrc;

												// 当上传完成后，用缓存图像加载服务器文件路径（因为加载需要时间）
												img.addEventListener(
													"load",
													function(){
														// 服务器图像加载完成，修改文档中插入图像的路径
														imageList.src = src;

														this.removeEventListener("load", arguments.callee);
													}
												);

												img.src = src;
											},
											// 进度回调函数
											function(progess){
												// 图片显示进度与上传进度一致
												imageList.src = fillDataByProgress(img, progess);
											}
										);

										imageList.src = fillDataByProgress(img, 0);
									}
								);

								img.src = data;
							}
						);
					}
				})[0]
			);
	};
	GroupByInsert = new Class(GroupByInsert, "jQun.GroupByInsert", ToolBarGroup.prototype);

	return GroupByInsert.constructor;
}(
	// anchorHtml
	new HTML([
		'<a',
			'href="{href}"',
			'title="超链接"',
			'contenteditable="false"',
			'style="text-decoration:underline;color:blue;cursor:pointer;"',
		'>{href}</a>'
	].join(" ")),
	open,
	// fillDataByProgress
	function(img, progress){
		var width = img.naturalWidth, height = img.naturalHeight,
		
			ch = round(height * progress), context = canvas.getContext("2d");

		canvas.width = width;
		canvas.height = height;

		context.fillStyle = "rgba(230, 230, 250, 0.3)";

		// 按道理重置宽高已经清空，为了确保新老宽高一样，再清空一次
		context.clearRect(0, 0, width, height);

		// 在高度为0的情况下绘制canvas许多浏览器会报错
		if(
			ch > 0
		){
			context.drawImage(img, 0, 0);
		}

		context.fillRect(0, ch, width, height - ch);

		return canvas.toDataURL();
	}
));

}.call(
	this,
	this.ToolBarGroup,
	jQun.ImageFileInput,
	this.SimpleButton,
	jQun.File,
	// canvas
	new HTML("<canvas></canvas>").create()[0],
	Math.round
));


// 编辑器工具栏相关
(function(GroupByDropDownList, GroupByColor, GroupByFontAppearance, GroupByAlign, GroupByInsert){

this.EditorToolBar = (function(){
	function EditorToolBar(selector, contentArea, _requestConnection){
		///	<summary>
		///	编辑器工具栏。
		///	</summary>
		///	<param name="selector" type="String, HTMLElement">元素选择器。</param>
		///	<param name="contentArea" type="ContentArea">编辑器内容区域。</param>
		///	<param name="_requestConnection" type="RequestConnection">上传图片使用的请求链接。</param>
		var ranges = [], input = null;
		
		this.attach({
			mousedown : function(e, targetList){
				if(
					targetList.get("tagName") === "INPUT"
				){
					if(
						targetList.get("type") === "text"
					){
						return;
					}
				}

				e.preventDefault();
			}
		});

		new GroupByDropDownList(
			this.query('>li[data-group="drop-down-list"]')[0],
			contentArea
		);

		new GroupByColor(
			this.query('>li[data-group="color-pick"]')[0],
			contentArea
		);

		new GroupByFontAppearance(
			this.query('>li[data-group="font-style"]')[0],
			contentArea
		);

		new GroupByAlign(
			this.query('>li[data-group="alignment"]')[0],
			contentArea
		);

		new GroupByInsert(
			this.query('>li[data-group="insert"]')[0],
			contentArea,
			_requestConnection
		);
	};
	EditorToolBar = new Class(EditorToolBar, "jQun.EditorToolBar", HTMLElementList.prototype);

	return EditorToolBar.constructor;
}());

}.call(
	this,
	this.GroupByDropDownList,
	this.GroupByColor,
	this.GroupByFontAppearance,
	this.GroupByAlign,
	this.GroupByInsert
));


// 编辑器相关
(function(EditorToolBar, ContentArea, CodeArea, JavaScriptCodeArea){

this.Editor = (function(CodeEditor, html){
	function Editor(_requestConnection, _disableEditable){
		///	<summary>
		///	编辑器类。
		///	</summary>
		///	<param name="_requestConnection" type="RequestConnection">上传图片使用的请求链接</param>
		///	<param name="_disableEditable" type="Boolean">内容是否可编辑</param>
		var contentArea = new ContentArea(_disableEditable);

		this.combine(
				html.create()
			)
			.assign(
				{
					contentArea : contentArea,
					toolBar : new EditorToolBar(
						this.query(">ul")[0],
						contentArea,
						_requestConnection
					)
				}
			);
		/*
		var regx = /a/;
		var a = new HTML('<div contenteditable="false"></div>').create();
		if(false){
			a && a || a;
		}
		var xml = new XMLHttpRequest();
		xml.onload = function(){
			new JavaScriptCodeArea()
				.setContent(this.response.split("\r\n", 30).join("\r\n"))
				.appendTo(a[0]);

			console.log(this.response.match(/\t./));
		};
		xml.open("get", "http://localhost/front/common/javascript/jQun.js", true);
		xml.send();

		
		a.appendTo(contentArea[0]);
		*/
		contentArea
			.appendTo(
				this.query(">blockquote")[0]
			);
	};
	Editor = new Class(Editor, "jQun.Editor", HTMLElementList.prototype);

	Editor.props({
		contentArea : null,
		toolBar : null
	});

	return Editor.constructor;
}(
	this.CodeEditor,
	// html
	new HTML([
		'<div class="editor" data-radius="small" data-border="black">',
			'<ul data-display="inline-block-container" data-bgcolor="deep-gray">',
				'@for(',
					'["drop-down-list", "color-pick", "font-style", "alignment", "insert"]',
				'->> group){',
					'<li data-group="{group}" data-display="inline-block-container"></li>',
				'}',
			'</ul>',
			'<blockquote></blockquote>',
		'</div>'
	].join(""))
));

}.call(
	this,
	this.EditorToolBar,
	this.ContentArea,
	this.CodeArea,
	this.JavaScriptCodeArea
));

defineProperties(jQun, this);
}(
	jQun,
	jQun.Class,
	jQun.StaticClass,
	jQun.Enum,
	jQun.Interface,
	jQun.HTMLElementList,
	jQun.HTML,
	jQun.Event,
	// LINE_BREAK
	"\r\n",
	document,
	// selection
	document.getSelection(),
	jQun.defineProperties
);